package com.huanyu.backend.study.datastructure.queue;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * 收银台排队结账的例子，基于用户商品一一扫描，使用阻塞队列
 * 阻塞队列不是立即从队列中添加或者删除元素，线程执行操作阻塞，直到有空间或者元素可用
 * 使用 ReentrantLock 锁实现阻塞
 *
 * ArrayBlockingQueue
 * 由数组支持的有界队列，可以选择是否需要公平性，
 * 如果公平参数被设置true，等待时间最长的线程会优先得到处理
 *
 * LinkedBlockingQueue
 * 由链接节点支持的可选有界队列，
 * 在不指定时容量为Integer.MAX_VALUE
 *
 * PriorityBlockingQueue
 * 由优先级堆支持的无界优先级队列，
 * 元素按优先级顺序被移除，而不是先进先出队列
 *
 * SynchronousQueue
 * 利用BlockingQueue接口的简单聚集机制
 *
 * DelayQueue
 * 由优先级堆支持的、基于时间的调度队列，基于PriorityQueue来实现的
 * 是一个存放Delayed 元素的无界阻塞队列，只有在延迟期满时才能从中提取元素
 * 该队列的头部是延迟期满后保存时间最长的 Delayed 元素
 * 如果延迟都还没有期满，则队列没有头部，并且poll将返回null
 * 当一个元素的 getDelay方法返回一个小于或等于零的值时，则出现期满，poll就可以移除这个元素
 * 此队列不允许使用 null 元素
 * @author arron
 */
public class PayExample {
    @AllArgsConstructor
    @NoArgsConstructor
    private static class Product {
        private String name;
        private BigDecimal price;
    }

    /**
     * 收银员
     */
    @AllArgsConstructor
    @NoArgsConstructor
    private static class Cashier {
        private String name;
        private volatile boolean stopScan = false;

        public Cashier(String name) {
            this.name = name;
        }

        private String scanProduct(Product product) {
            try {
                // 休息2毫秒模拟扫描消耗
                TimeUnit.MILLISECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Cashier " + name + " scan Product: name(" + product.name + "), price(" + product.price + ")";
        }

        private void stopScan(BlockingQueue<Product> blockingQueue) {
            stopScan = true;
            if (blockingQueue != null) {
                try {
                    blockingQueue.put(new Product("stop", new BigDecimal("0")));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 一个用户购买多件商品，一个收银员扫描商品
     */
    public static void manyProductAndSingleCashierUseArrayBlockingQueue() {
        // 多个商品放在购物台上 最多同时放5件商品
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        ArrayBlockingQueue<Product> arrayBlockingQueue = new ArrayBlockingQueue<>(5);
        Cashier cashier = new Cashier("arrayBlockingQueueCashier");
        executorService.submit(() -> {
            // 用户不断从购物篮中取出商品放在购物台上
            for (Product product: initShoppingBasket()) {
                try {
                    arrayBlockingQueue.put(product);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        executorService.submit(() -> {
            while (!cashier.stopScan) {
                String scanResult = null;
                try {
                    scanResult = cashier.scanProduct(arrayBlockingQueue.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(scanResult);
            }
            System.out.println("Cashier stop scan any product!");
        });

        // 模拟收银员停止结账等待1秒钟
        try {
            TimeUnit.MILLISECONDS.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cashier.stopScan(arrayBlockingQueue);
        executorService.shutdown();
    }

    /**
     * 用户浏览商品加入购物篮
     * @return
     */
    private static List<Product> initShoppingBasket() {
        List<Product> shoppingBasket = new ArrayList<>(10);
        Product one = new Product("one", new BigDecimal("1"));
        shoppingBasket.add(one);
        Product two = new Product("two", new BigDecimal("2"));
        shoppingBasket.add(two);
        Product three = new Product("three", new BigDecimal("3"));
        shoppingBasket.add(three);
        Product four = new Product("four", new BigDecimal("4"));
        shoppingBasket.add(four);
        Product five = new Product("five", new BigDecimal("5"));
        shoppingBasket.add(five);
        Product six = new Product("six", new BigDecimal("6"));
        shoppingBasket.add(six);
        Product seven = new Product("seven", new BigDecimal("7"));
        shoppingBasket.add(seven);
        Product eight = new Product("eight", new BigDecimal("8"));
        shoppingBasket.add(eight);
        Product nine = new Product("nine", new BigDecimal("9"));
        shoppingBasket.add(nine);
        Product ten = new Product("ten", new BigDecimal("10"));
        shoppingBasket.add(ten);
        return shoppingBasket;
    }

    public static void main(String[] args) {
        manyProductAndSingleCashierUseArrayBlockingQueue();
    }
}
